import Piece from './logic/piece'
import Button from './logic/button'
import BackGround from './logic/background'
import GameInfo from './logic/gameinfo'

let instance

const LENGTH = 6

/**
 * 全局状态管理器
 */
export default class DataBus {
  constructor(ctx) {
    if (instance) return instance
    instance = this

    this.bg = new BackGround(ctx)
    this.gameinfo = new GameInfo()
    this.size = this.bg.cellSize
    this.area = this.bg.area
  }

  reset() {
    this.gameinfo.setLevel()
    this.buttons = []

    this.btnPrev = new Button(window.innerWidth / 4 - 70, this.area.endy + 40, 100, 45, "上一关")
    this.btnPrev.onclick(this.resetPrve.bind(this))
    this.btnRestart = new Button(window.innerWidth / 2 - 50, this.area.endy + 40, 100, 45, "重新开始")
    this.btnRestart.onclick(this.resetLevel.bind(this))
    this.btnNext = new Button(window.innerWidth / 4 * 3 - 30, this.area.endy + 40, 100, 45, "下一关")
    this.btnNext.onclick(this.resetNext.bind(this))
    this.buttons.push(this.btnPrev, this.btnRestart, this.btnNext)
    this.resetLevel()
  }

  resetPrve() {
    if (this.gameinfo.prevLevel()) {
      this.initLevel()
    }
  }
  resetNext() {
    if (this.gameinfo.nextLevel()) {
      this.initLevel()
    }
  }

  resetLevel() {
    this.gameinfo.setLevel()
    this.initLevel()
  }

  initLevel() {
    this.current = null
    this.list = []
    this.pieces = []
    this.gameinfo.mapData.split(',').forEach((x, index) => {
      this.parsePiece(index, x)
    })
    this.btnNext.disabled = !this.gameinfo.bestStep
  }

  parsePiece(id, bkData) {
    let type = bkData[0]
    let x = parseInt(bkData[1])
    let y = parseInt(bkData[2])
    let height = this.size
    let width = this.size
    let pos = y * LENGTH + x
    let arr = [pos]
    if (type == '0') {
      width = 2 * this.size
      arr.push(pos + 1)
    } else if (type == '1') {
      width = 3 * this.size
      arr.push(pos + 1, pos + 2)
    } else if (type == '2') {
      height = 2 * this.size
      arr.push(pos + LENGTH)
    } else if (type == '3') {
      height = 3 * this.size
      arr.push(pos + LENGTH, pos + LENGTH + LENGTH)
    }
    let sx = x * this.size + this.area.startx - 1
    let sy = y * this.size + this.area.starty - 1
    let p = new Piece(id, type, sx, sy, width, height, arr)
    this.pieces.push(p)
    this.list.push(...arr)
  }

  render(ctx) {
    this.bg.render(ctx)
    this.pieces.forEach(p => p.render(ctx))
    this.buttons.forEach(b => b.render(ctx))
    this.gameinfo.renderGameInfo(ctx)
  }
  update() {
    this.pieces.forEach((p) => {
      p.update()
    })
  }

  click(x, y) {
    let hasPiece = this.moveStart(x, y)
    if (!hasPiece) {
      for (let i = 0; i < this.buttons.length; i++) {
        const btn = this.buttons[i];
        if (btn.click(x, y)) {
          break
        }
      }
    }
  }

  moveStart(x, y) {
    if (this.gameinfo.isFinished) {
      return
    }
    for (let i = 0; i < this.pieces.length; i++) {
      let p = this.pieces[i]
      if (p.contains(x, y)) {
        this.current = p
        this.current.isMoving = true
        this.sx = this.lastx = x
        this.sy = this.lasty = y
        this.offleft = x - this.current.startx
        this.offright = x - this.current.endx
        this.offtop = y - this.current.starty
        this.offbottom = y - this.current.endy
        this.delta = 0
        this.current.indexes.forEach(x => {
          let index = this.list.indexOf(x)
          this.list.splice(index, 1)
        })
        this.padding = this.getPadding()
        return true
      }
    }
    this.current = null
    return false
  }
  moving(x, y) {
    if (this.gameinfo.isFinished) {
      return
    }
    if (this.current) {
      if (!this.canMove(x, y)) {
        return
      }
      if (this.current.isHorizontal) {
        this.current.movex = x - this.sx
        this.current.speed = x - this.lastx
        this.lastx = x
      } else {
        this.current.movey = y - this.sy
        this.current.speed = y - this.lasty
        this.lasty = y
      }
    }
  }
  moveEnd(x, y) {
    if (this.gameinfo.isFinished) {
      return
    }
    if (this.current && this.current.isMoving) {
      let dx = 0
      let dy = 0
      if (this.delta != 0) {
        if (this.current.isHorizontal) {
          dx = this.delta * this.size
        } else {
          dy = this.delta * this.size
          this.delta = this.delta * LENGTH
        }
        for (let i = 0; i < this.current.indexes.length; i++) {
          this.current.indexes[i] += this.delta
        }
        this.gameinfo.step++
      }
      if (this.current.id == 0 && this.current.indexes.indexOf(18) > 0) {
        this.gameinfo.finish()
        this.btnNext.disabled = false
      }
      this.list.push(...this.current.indexes)
      this.current.moveTo(dx, dy)
    }
    this.delta = 0
    this.current = null
  }

  canMove(x, y) {
    if (this.current.isHorizontal) {
      if (x - this.offleft <= this.area.startx) {
        return false
      }
      if (this.current.id != 0 && x - this.offright > this.area.endx) {
        return false
      }
      if (this.padding.left == 0 && this.padding.right == 0) {
        return false
      }
      if (x - this.sx > this.padding.right * this.size) {
        return false
      }
      if (this.sx - x > this.padding.left * this.size) {
        return false
      }
      if (x - this.sx - this.size / 2 > 0) {
        this.delta = parseInt((x - this.sx + this.size / 2) / this.size)
      } else if (x - this.sx + this.size / 2 < 0) {
        this.delta = parseInt((x - this.sx - this.size / 2) / this.size)
      } else {
        this.delta = 0
      }
    } else {
      if (y - this.offtop <= this.area.starty || y - this.offbottom > this.area.endy) {
        return false
      }
      if (this.padding.top == 0 && this.padding.bottom == 0) {
        return false
      }
      if (y - this.sy > this.padding.bottom * this.size) {
        return false
      }
      if (this.sy - y > this.padding.top * this.size) {
        return false
      }
      if (y - this.sy - this.size / 2 > 0) {
        this.delta = parseInt((y - this.sy + this.size / 2) / this.size)
      } else if (y - this.sy + this.size / 2 < 0) {
        this.delta = parseInt((y - this.sy - this.size / 2) / this.size)
      } else {
        this.delta = 0
      }
    }
    return true
  }

  isEmpty(delta) {
    for (let i = 0; i < this.current.indexes.length; i++) {
      let item = this.current.indexes[i] + delta
      if (this.list.indexOf(item) > -1) {
        return false
      }
    }
    return true
  }

  getPadding() {
    let padding = {
      left: 0,
      top: 0,
      right: 0,
      bottom: 0
    }
    let min = this.current.indexes[0]
    let max = this.current.indexes[this.current.indexes.length - 1]
    if (this.current.isHorizontal) {
      let row = parseInt(min / LENGTH)
      for (let i = 1; i < LENGTH - 1; i++) {
        if (parseInt((min - i) / LENGTH) != row) {
          break
        }
        if (this.list.indexOf(min - i) > -1) {
          break
        }
        padding.left = i
      }
      for (let i = 1; i < LENGTH; i++) {
        if (this.current.id != 0) {
          if (parseInt((max + i) / LENGTH) != row) {
            break
          }
          if (this.list.indexOf(max + i) > -1) {
            break
          }
        } else {
          if (max + i > 18) {
            break
          }
          if (max + i != 18) {
            if (this.list.indexOf(max + i) > -1) {
              break
            }
          }
        }
        padding.right = i
      }
    } else {
      for (let i = 1; i < LENGTH - 1; i++) {
        if (min - LENGTH * i < 0) {
          break
        }
        if (this.list.indexOf(min - LENGTH * i) > -1) {
          break
        }
        padding.top = i
      }
      for (let i = 1; i < LENGTH - 1; i++) {
        if (max + LENGTH * i >= LENGTH * LENGTH) {
          break
        }
        if (this.list.indexOf(max + LENGTH * i) > -1) {
          break
        }
        padding.bottom = i
      }
    }
    //console.log(padding, this.list, this.current.indexes, this.current.id, this.current.startx, this.current.starty, this.current.width, this.current.height)
    return padding
  }
}